feat(container-runtime): expose versionMarkResolver on IContainerRuntime - #28168
feat(container-runtime): expose versionMarkResolver on IContainerRuntime#28168lindsnguyen wants to merge 7 commits into
Conversation
|
Hi! Thank you for opening this PR. Want me to review it? Based on the diff (445 lines, 15 files), I've queued these reviewers:
How this works
|
There was a problem hiding this comment.
🟡 Changes recommended
@fluidframework/container-runtime currently re-exports the version-mark types from @fluidframework/container-runtime-definitions/internal, which conflicts with the stated intent to re-export from /legacy and may leak an internal module specifier into the public .d.ts surface.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR graduates the version mark resolver to a supported host-facing access point by adding versionMarkResolver to the @legacy @beta IContainerRuntime interface, moving the related public types into @fluidframework/container-runtime-definitions, and extending ResolveResult with an optional diagnostic reason?: string for pending/unresolvable.
Changes:
- Move
IVersionMarkResolver,ResolveResult, andVersionMarkCaptureinto@fluidframework/container-runtime-definitionsand export them from the package entrypoint. - Add
versionMarkResolver: IVersionMarkResolvertoIContainerRuntimeso hosts no longer need to cast toContainerRuntime. - Add an optional
reason?: stringtoResolveResult’spending/unresolvableoutcomes and update runtime + tests/docs accordingly.
File summaries
| File | Description |
|---|---|
| packages/runtime/container-runtime/src/versionMarks/versionMarkResolver.ts | Switches to imported shared public types and adds reason when returning pending/unresolvable. |
| packages/runtime/container-runtime/src/versionMarks/index.ts | Re-exports version mark public types from container-runtime-definitions and continues exporting the implementation. |
| packages/runtime/container-runtime/src/versionMarks/DEV.md | Updates design notes to document the reason?: string policy and improves formatting. |
| packages/runtime/container-runtime/src/test/versionMarks/versionMarkResolver.spec.ts | Updates expected ResolveResult shapes to include reason where applicable. |
| packages/runtime/container-runtime/src/test/containerRuntime.spec.ts | Updates the loader-compat regression test to expect reason: "historicalOpsUnavailable". |
| packages/runtime/container-runtime/api-report/container-runtime.legacy.beta.api.md | Updates legacy beta API report for ResolveResult.reason?: string. |
| packages/runtime/container-runtime/api-report/container-runtime.legacy.alpha.api.md | Updates legacy alpha API report for ResolveResult.reason?: string. |
| packages/runtime/container-runtime-definitions/src/versionMarks.ts | Introduces the new shared @legacy @beta version mark API types and docs. |
| packages/runtime/container-runtime-definitions/src/test/types/validateContainerRuntimeDefinitionsPrevious.generated.ts | Updates type-test baseline to acknowledge expected forward-compat break for the deprecated interface. |
| packages/runtime/container-runtime-definitions/src/index.ts | Re-exports the new version mark types from the package root. |
| packages/runtime/container-runtime-definitions/src/containerRuntime.ts | Adds versionMarkResolver to IContainerRuntime. |
| packages/runtime/container-runtime-definitions/package.json | Marks the known forward-compat break for the deprecated interface in typeValidation.broken. |
| packages/runtime/container-runtime-definitions/api-report/container-runtime-definitions.legacy.beta.api.md | Adds the new IVersionMarkResolver/ResolveResult/VersionMarkCapture exports and IContainerRuntime.versionMarkResolver. |
| .changeset/version-mark-resolver-access-point.md | Adds changeset for the API surface move and ResolveResult.reason. |
Review details
- Files reviewed: 14/14 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Deep Review: The diagnostic Consequence: the runtime never logs why a mark did not resolve — every host must forward the field itself. The value is low-cardinality and PII-free, so add it to the payload under the same "diagnostic-only, unstable, log-only" rule already applied to the returned field, and update the shape assertion in the spec. If the omission is intentional (cardinality/PII), document it as a non-goal instead — and state the supported way for the runtime, not each host, to log the non-resolution cause. |
|
Deep Review: This PR adds Rewrite the "Host exposure" paragraph (lines 92-95) to state the resolver is now exposed on the |
Daniel Madrid (dannimad)
left a comment
There was a problem hiding this comment.
Do not merge until bump PR is merged first
42ca62f to
dd89cdc
Compare
Joshua Smithrud (Josmithr)
left a comment
There was a problem hiding this comment.
Approving docs changes. I didn't review the code or API changes in much detail (I see that Tony has already been reviewing those).
542b451 to
6d76d41
Compare
| * Licensed under the MIT License. | ||
| */ | ||
|
|
||
| export type { |
There was a problem hiding this comment.
Are we going to remove these exports after moving consumers over to import directly from the definitions package? (Will be moot if we go with a free function that extracts the IVersionMarkResolver from an IContainerRuntime like my other comment suggests - just export that function from here along with these types)
There was a problem hiding this comment.
yeah we'll remove it after the shim on the office bohemia side is removed during a release where beta breaking changes (maybe 3.10?). I filed a workitem: https://dev.azure.com/fluidframework/internal/_workitems/edit/82953 so we don't forget
1a927fc to
4a8733e
Compare
| * typeValidation.broken: | ||
| * "Interface_IContainerRuntimeWithResolveHandle_Deprecated": {"forwardCompat": false} | ||
| */ | ||
| // @ts-expect-error compatibility expected to be broken |
There was a problem hiding this comment.
Still needed, not out of date. IContainerRuntimeWithResolveHandle_Deprecated is non-sealed and extends IContainerRuntime, so it transitively inherits the new versionMarkResolver member, and its forward-compat direction breaks against the previous release. I confirmed by removing the @ts-expect-error and rebuilding: the type-test fails with an assignability error on the old_as_current check.
| "typeValidation": { | ||
| "broken": {}, | ||
| "broken": { | ||
| "Interface_IContainerRuntimeWithResolveHandle_Deprecated": { |
There was a problem hiding this comment.
same as above, we need this since it's not sealed.
Move the version mark resolver public types (IVersionMarkResolver, ResolveResult, VersionMarkCapture) into container-runtime-definitions and expose `versionMarkResolver` on the `@legacy @beta` IContainerRuntime interface, so hosts obtain the resolver from a supported interface rather than the concrete `@internal` ContainerRuntime class. container-runtime re-exports the types for back-compat; its API surface is unchanged.
…nding/unresolvable ResolveResult - `reason` is an opaque, log-only diagnostic string, not a typed union. The runtime currently sets `awaitingSequence` / `historicalOpsUnavailable` on `pending` and `historyTrimmed` on `unresolvable`. - Hosts drive all behavior from `kind` and must not branch on `reason`. A plain string keeps additions non-breaking and avoids a second de facto discriminator; a future state needing different behavior should be a new `kind`, not a new `reason`. - `reason` is transient operational context, not persisted. office-bohemia will log it when moving to the supported access point but makes no behavioral change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
0a2cbef to
6c241d7
Compare
|
🔗 No broken links found! ✅ Your attention to detail is admirable. linkcheck output |
Deep ReviewReviewed commit Readiness: 10/10 — READY Ready for human review. The redundant compatibility re-export file from the prior review is removed; the supported Context for Reviewers
For human reviewer
Review history (5 prior reviews)
|
Bundle size comparisonBase commit: Notable changesNo bundles changed by ≥ 500 bytes parsed. Per-bundle deltas
|
Description
Expose the version mark resolver as a supported access point on the
@legacy @betaIContainerRuntimeBaseinterface, so hosts obtain it from a supported interface instead of casting the concrete@internalContainerRuntimeclass. Fixes AB#82258.versionMarkResolver: IVersionMarkResolvertoIContainerRuntimeBase.IVersionMarkResolver,ResolveResult,VersionMarkCapture) into@fluidframework/runtime-definitions(the package that ownsIContainerRuntimeBase).@fluidframework/container-runtimere-exports them, so existing consumers importing from@fluidframework/container-runtime/legacyare unchanged.ResolveResult'spendingandunresolvableresults carry an optionalreason?: string, an opaque log-only diagnostic. Hosts drive behavior fromkind.reasonis not a contract and must not be branched on.Why the resolver moved from
IContainerRuntimetoIContainerRuntimeBaseThis work started by exposing
versionMarkResolveronIContainerRuntime. During review we found that the actual host consumer (office-bohemia) reaches the runtime from inside aDataObject, throughIFluidDataStoreContext.containerRuntime, which is typedIContainerRuntimeBase, notIContainerRuntime. A data store is only ever handed the base surface by contract, so exposing the resolver onIContainerRuntimealone would force the consumer to keep an unsafeas IContainerRuntimedowncast past its own contract.Placing the member on
IContainerRuntimeBase(whichIContainerRuntimeextends, so host-level callers still see it) lets the real consumer read it type-safely and removes the shim entirely. This also matches the role of the base as the data-store-facing service surface.How this removes the office-bohemia shim
Because the resolver was not on any importable interface, office-bohemia consumes it today through a structural duck-typing shim (
packages/base-container/src/VersionMarkService.ts):With the resolver on
IContainerRuntimeBase, that collapses to a typed access with no probe and no cast:The office-bohemia cleanup is a separate follow-up. This PR only makes it possible. It does not break office-bohemia before they take it: their imports still resolve, and their existing runtime probe keeps working because it duck-types the concrete runtime rather than a declared type.
Reviewer Guidance
IContainerRuntimeBaserather thanIContainerRuntimeon purpose: the supported consumer is aDataObjectthat only seesIContainerRuntimeBaseviaIFluidDataStoreContext.containerRuntime. Exposing it only onIContainerRuntimewould leave that consumer casting.IContainerRuntimeBaseandIContainerRuntimeare both@sealed, so their type-tests only run the backCompat direction (a current instance must satisfy the older type). Adding a required member keepscurrentassignable to the narrowerold, so that direction still passes. The forward-compat direction (where a new required member would break an old implementer) is not run for sealed interfaces, so no acknowledgment is required. Moving the member between the two sealed interfaces produces no type-test diff.IContainerRuntimeWithResolveHandle_Deprecatedruns both directions, so its forward-compat break stays acknowledged viatypeValidation.broken.@fluidframework/runtime-definitionsbecauseIContainerRuntimeBaselives there and cannot depend oncontainer-runtime-definitions.container-runtimere-exports them so@fluidframework/container-runtime/legacyconsumers are unchanged.reason?: stringis deliberately a plain string, not a string-literal union, so it stays diagnostic-only and additions never break exhaustivekindconsumers.